tools/utils/src/modules/[any-number]-[array-number].js

/**
* @module @svizzle/utils/[any-number]-[array-number]
*/

import * as _ from 'lamb';

import {getLength} from './iterable-number.js';
import {isNumber} from './any-boolean.js';

/**
 * Return a function expecting an array of objects and returning the max of results
 * of applying the provided fuction on all of the array items
 * @see
 {@link module:@svizzle/utils/array-number.arrayMax|arrayMax},
 {@link module:@svizzle/utils/string-[array-number].arrayMaxBy|arrayMaxBy}
 *
 * @function
 * @arg {function} fn
 * @return {function} - Array -> Number
 *
 * @example

> maxWithAbsSin = arrayMaxWith(_.pipe([Math.sin, Math.abs]))
> maxWithAbsSin([-Math.PI/2, -Math.PI/4])
1
> maxWithAbsSin([Math.PI/4, Math.PI/6])
0.7071067811865475

 *
 * @since 0.1.0
 */
export const arrayMaxWith = fn => _.reduceWith((max, item) => {
	const value = fn(item);

	return value > max ? value : max;
}, -Infinity);

/**
 * Return a function expecting an array of objects and returning the min of results
 * of applying the provided fuction on all of the array items
 * @see
 {@link module:@svizzle/utils/array-number.arrayMin|arrayMin},
 {@link module:@svizzle/utils/string-[array-number].arrayMinBy|arrayMinBy}
 *
 * @function
 * @arg {function} fn
 * @return {function} - Array -> Number
 *
 * @example
> minWithAbsSin = arrayMinWith(_.pipe([Math.sin, Math.abs]))
> minWithAbsSin([-Math.PI/2, -Math.PI/4])
0.7071067811865475
> minWithAbsSin([Math.PI/4, Math.PI/6])
0.49999999999999994
 *
 * @since 0.1.0
 */
export const arrayMinWith = fn => _.reduceWith((min, item) => {
	const value = fn(item);

	return value < min ? value : min;
}, Infinity);

/**
 * Return a function expecting an array and summing the numbers obtained
 * from applying the provided accessor to the array items.
 * Note that it skips items where the accessor does not return a number.
 *
 * @function
 * @arg {function} accessor - Any -> Number
 * @return {function} - Array -> Number
 *
 * @example
> sumValues = arraySumWith(_.getKey('a'))
> sumValues([{a: 1}, {a: 2}, {a: 3}])
6
> sumValues([{a: 1}, {a: 2}, {a: 'hey'}])
3
> sumValues([{a: 1}, {a: 2}, {notA: 3}])
3
> sumValues([{a: 'hey'}, {notA: 'b'}, {notA: 3}])
0
> sumValues([])
0
 *
 * @since 0.16.0
 * @see {@link module:@svizzle/utils/array-number.arraySum|arraySum}
 */
export const arraySumWith = accessor => _.reduceWith((acc, item) => {
	const value = accessor(item);

	return acc + (isNumber(value) ? value : 0);
}, 0)

/**
 * Return the average of values of a {key, value}[] array
 *
 * @function
 * @arg {function} accessor - Any -> Number
 * @return {function} - Array -> Number
 *
 * @example
> makeAverageOfA = makeAverageWith(_.getKey('a'));
> makeAverageOfA([
	{a: 1, b: 2},
	{a: 10, b: 7},
	{a: 7, b: 9},
])
6
> makeAverageOfA([])
0
 *
 * @since 0.11.0
 */
export const makeAverageWith = accessor => _.pipe([
	_.collect([arraySumWith(accessor), getLength]),
	([sum, length]) => length ? sum / length : 0
]);